#include <bitset>
#include <stdexcept>
#include "Polygon.h"
#include "Definitions.h"
#include "Buffer.h"
#include "Config.h"
#include "Line.h"

/*********************************************/
void Polygon::clip2DCohenSutherland() const
{
	Config*		cfg = Config::Instance();
	uint32		width = cfg->get("INTERNAL_SCREEN_WIDTH");
	uint32 		height = cfg->get("INTERNAL_SCREEN_HEIGHT");
	Point3D		clipVertex[3];
	uint16		clipCount=0;
	std::bitset<4>	pClass[3];

	// Initialize the bitset to 0000
	for ( uint16 i = 0; i<3; i++ ) {
		pClass[i] = p[i].getClipCode();
	}

	// Areas:
	/*   1001 | 0001 | 0101
	    ------+------+------
	     1000 | 0000 | 0100
	    ------+------+------
	     1010 | 0010 | 0110
	*/

	clipCount = 0;
	for ( uint16 i = 0; i<3; i++ ) {
		if ((pClass[i] & std::bitset<4>(std::string("0001"))) == std::bitset<4>(std::string("0001"))) {
			clipVertex[i] = p[i];
			clipCount++;
		}
	}
	if ( clipCount == 2 ) {
		
	}
}
/*********************************************/
void Polygon::drawWireFrame() const
{
	Line3D(p[0],p[2]).draw();
	Line3D(p[1],p[2]).draw();
	Line3D(p[2],p[0]).draw();
}
/*********************************************/
void Polygon::drawZBuffer() const
{
	Config*		cfg = Config::Instance();
	BufferControl*	bControl = BufferControl::Instance();

	Point3D			L, R, p[3];
	Color 			LColor, RColor, pixColor;
	uint32			largest, smallest, center;
	uint32			width = cfg->get("INTERNAL_SCREEN_WIDTH");
	uint32 			height = cfg->get("INTERNAL_SCREEN_HEIGHT");
	ImageBuffer<fl64>*	depth = bControl->getDepthBuffer();
	ImageBuffer<Color>*	frame = bControl->getFrameBuffer();

	// We need some temporary transformations on the vertices so we copy them to a local array
	for ( uint32 i = 0; i<3; i++ ) p[i] = Polygon::p[i];

	// Find the deepest vertex
	Point3D	deepest = p[0];
	if ( p[1].z > deepest.z ) deepest = p[1];
	if ( p[2].z > deepest.z ) deepest = p[2];

	// Get the largest Y value
	if 		(( p[0].y >= p[1].y ) && ( p[0].y >= p[2].y )) largest = 0;
	else if 	(( p[1].y >= p[0].y) && ( p[1].y >= p[2].y )) largest = 1;
	else		largest = 2;

	// Get the smallest Y value
	if 		(( p[0].y <= p[1].y ) && ( p[0].y <= p[2].y )) smallest = 0;
	else if 	(( p[1].y <= p[0].y ) && ( p[1].y <= p[2].y )) smallest = 1;
	else		smallest = 2;

	// Get the center Y value
	if ((( largest == 0 ) && (smallest == 1 )) || (( largest == 1 ) && (smallest == 0 ))) center = 2;
	else if ((( largest == 2 ) && (smallest == 1 )) || (( largest == 1 ) && (smallest == 2 ))) center = 0;
	else center = 1;
	
	// Correct negative / to large values ( height only ! )
	if ( p[smallest].y < 0 ) p[smallest].y = 0;
	if ( p[largest].y > height ) p[largest].y = height;
	if ( p[smallest].y > height ) return;
	if ( p[largest].y < 0 ) return;

	// Create an array with the indices for the scale order
	uint32 order[3] = { smallest, center, largest };

	for ( uint16 i = 0; i<2; i++ ) {
		for ( uint16 scanline = p[order[i]].y; scanline <= p[order[i+1]].y; scanline++ ) {
			fl64 lambda = (scanline - p[order[i]].y)/(p[order[i+1]].y - p[order[i]].y);
			fl64 rho = (scanline - p[smallest].y)/(p[largest].y - p[smallest].y);
			
			// interpolation guards ( avoid lines that go to infinity or outside the polygon )
			if ( lambda > 1.0f ) lambda = 1.0f;
			if ( lambda < 0.0f ) lambda = 0.0f;
			if ( rho > 1.0f ) rho = 1.0f;
			if ( rho < 0.0f ) rho = 0.0f;

			// Calculate Left and Right points ( scanline limits )
			L.x = p[order[i]].x + lambda*(p[order[i+1]].x - p[order[i]].x);
			L.z = p[order[i]].z + lambda*(p[order[i+1]].z - p[order[i]].z);
			LColor = ((c[order[i+1]]*lambda) + (c[order[i]]*(1-lambda)));

			R.x = p[smallest].x + rho*(p[largest].x - p[smallest].x);
			R.z = p[smallest].z + rho*(p[largest].z - p[smallest].z);
			RColor = ((c[largest]*rho) + (c[smallest]*(1-rho)));

			// Screen size guards ( horizontal check )
			if ( L.x < 0 ) L.x = 0; 
			if ( R.x < 0 ) R.x = 0;
			if ( L.x > width ) L.x = width;
			if ( R.x > width ) R.x = width;
				
			// draw scanline + depthtest
			if ( L.x > R.x ) {
				Point3D		temp = R;
				Color		cTemp = RColor;
				R = L;
				L = temp;
				RColor = LColor;
				LColor = cTemp;
			}

			for ( uint16 slPx = L.x; slPx < R.x; slPx++) {
				fl64 alpha = ( slPx - L.x )/( R.x - L.x );
				fl64 newDepth = L.z + alpha*(R.z - L.z);
				
				if ( alpha > 1.0f ) alpha = 1.0f;
				if ( alpha < 0.0f ) alpha = 0.0f;
				if ( newDepth > deepest.z ) newDepth = deepest.z;

				if ( depth->getPixel( slPx, scanline ) > newDepth) {
					if ( uniform ) {
						frame->setPixel( slPx, scanline, c[0] );
					} else {
						pixColor = ((RColor*alpha) + (LColor*(1-alpha)));
						frame->setPixel( slPx, scanline, pixColor );
					}
					depth->setPixel( slPx, scanline, newDepth );
				}
			}
		}
	}
}
/*********************************************/
Color Polygon::getColor( const fl64 u, const fl64 v ) const
{
	return( c[0] );
}
/*********************************************/
void Polygon::setUniformColor( const Color& c )
{
	for ( uint32 i = 0; i<3; i++ ) {
		Polygon::c[i] = c;
	}
	uniform = true;
}
/*********************************************/
void Polygon::setColor( const uint32& pIndex, const Color& c )
{
	Polygon::c[pIndex] = c;
	uniform = false;
}
/*********************************************/
void Polygon::setPoint( uint8 index, Point3D p )
{
	if (( index > 2 ) || ( index < 0 ))
		throw( std::logic_error("Illegal index request to a triangle object!"));
	
	Polygon::p[index] = p; 
}
/*********************************************/
Point3D Polygon::getPoint( uint8 index ) const
{
	if (( index > 2 ) || ( index < 0 ))
		throw( std::logic_error("Illegal index request to a triangle object!"));
	
	return( p[index] );
}
/*********************************************/
void Polygon::operator=(const Polygon& triangle )
{
	for ( uint32 i = 0; i<3; i++ ) {
		p[i] = triangle.p[i];
		c[i] = triangle.c[i];
	}
	uniform = triangle.uniform;
}
/*********************************************/
Polygon::Polygon()
{
	uniform = false;
}
/*********************************************/
Polygon::Polygon(const Polygon& triangle)
{
	for ( uint32 i = 0; i<3; i++ ) {
		p[i] = triangle.p[i];
		c[i] = triangle.c[i];
	}
	uniform = triangle.uniform;
}
/*********************************************/
Polygon::~Polygon()
{
}